feat(cm): mirror the critical-minerals workbook into CM_legacy - #867
Draft
jirhiker wants to merge 5 commits into
Draft
feat(cm): mirror the critical-minerals workbook into CM_legacy#867jirhiker wants to merge 5 commits into
jirhiker wants to merge 5 commits into
Conversation
Phase-1 staging mirror of the Earth MRI critical-minerals chemistry workbook (McLemoreMasterChem, NMBGMR) into CM_* tables, plus a reconciliation report for the source-data owner. No transform into the Ocotillo data model yet. Seven mirror tables. The ChemicalData, GIS and QAQC sheets share a column set, so they land in one CM_ChemicalData behind a source_sheet discriminator: the first two are byte-identical in header text and order, and QAQC is those columns minus MapSymbol/Pd/Pt with latitude/longitude capitalized. Rows are keyed on (source_sheet, source_row) rather than SAMPLE, which repeats across 258 names. GIS is a stale hand-maintained fork of ChemicalData, not a location-enriched copy: it carries the same mixed datums and the same ~876 rows with no latitude, and 1704 of the 4848 shared rows disagree in both directions (GIS holds 533 Chem Lab File No., 485 Laboratory and 170 FeO/Fe2O3 values ChemicalData lacks; ChemicalData holds 633 Total, 184 Area and 18 appended Pearce (2020) samples GIS lacks). Neither sheet is authoritative, so both are mirrored in full and reconciliation is deferred to a per-column ruling by V.T. McLemore. The loader warns whenever the two row counts diverge. Every column is a String. The workbook stores censored analyte values as text (1154 '<' values in Au alone), carries '#VALUE!' errors, and mixes real dates with year-only text; parsing belongs to the transform. Column names are derived mechanically because sheet headers are not SQL identifiers, with analytes carrying the unit the workbook declares for them - which also keeps As and In off the Python and SQL keyword lists. Loading is idempotent per sheet and asserts the layout instead of guessing it: a moved header row or an unmapped column aborts the load rather than silently dropping cells. scripts/cm_reconciliation_report.py builds the decision workbook - the sheet-to-sheet drift plus 4276 integrity findings across 13 issue types, among them 724 values impossible for their declared unit (F = 27700 in a % column), 259 Au detection limits that are ppm in a ppb column, 300 non-numeric analyte tokens, and 64 of 84 analytes reported against more than three distinct detection limits. Verified by loading the delivered workbook: 10,100 rows, and 708 cells across 6 random rows compared against openpyxl with zero mismatches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Coverage✅ 79.55% total — gate is 75%. Coverage for the Python files changed in this PR
|
1 similar comment
Contributor
Coverage✅ 79.55% total — gate is 75%. Coverage for the Python files changed in this PR
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a Phase-1 staging mirror for the Earth MRI critical-minerals chemistry workbook (McLemoreMasterChem) into new CM_* PostgreSQL tables, plus a reconciliation workbook generator to help the source-data owner resolve ChemicalData vs GIS drift before any transform into the Ocotillo domain model.
Changes:
- Introduces
CM_legacySQLAlchemy models and an Alembic migration creating 7 mirror tables (string-typed, keyed by(source_sheet, source_row)where applicable). - Implements an idempotent workbook loader (
services/cm_legacy_mirror.py) and exposes it via a new CLI command (oco load-critical-minerals-workbook). - Adds a reconciliation report script and comprehensive tests + documentation for the new mirror layer.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
db/cm_legacy.py |
Defines CM_* mirror models and source-header/unit mappings for faithful workbook mirroring. |
alembic/versions/d4e5f6a7b8c9_cm_legacy_mirror_tables.py |
Creates the CM_* mirror tables and indexes via migration. |
services/cm_legacy_mirror.py |
Loads workbook sheets into mirror tables (string rendering + per-sheet idempotency). |
cli/cli.py |
Adds oco load-critical-minerals-workbook --file <xlsx> to run the mirror load. |
scripts/cm_reconciliation_report.py |
Generates a decision workbook comparing ChemicalData vs GIS and integrity findings. |
docs/critical-minerals-legacy-mirror.md |
Documents the mirror design, drift rationale, and reconciliation workflow. |
tests/test_cm_legacy.py |
Tests loader behavior, schema/header mappings, and idempotent per-sheet reload semantics. |
tests/test_cm_reconciliation_report.py |
Tests reconciliation workbook structure, counts, and integrity classifications. |
db/__init__.py |
Registers the new cm_legacy models in the DB package imports. |
CLAUDE.md |
Adds repository guidance/pointer for the new CM_legacy mirror layer. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Contributor
Coverage✅ 79.54% total — gate is 75%. Coverage for the Python files changed in this PR
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase-1 staging mirror of the Earth MRI critical-minerals chemistry workbook (
McLemoreMasterChem, compiled by V.T. McLemore et al., NMBGMR) intoCM_*tables, plus a reconciliation report for the source-data owner. No transform into the Ocotillo data model yet.What changed
Seven mirror tables, following the
db/nma_legacy.py/db/nmw_legacy.pyconvention:CM_ChemicalData(source_sheetdiscriminator)CM_DetectionLimitsCM_ReferencesCM_MineralSystemsCM_WorldComparisons/CM_WorldReferencesCM_WorkbookMetadatadb/cm_legacy.py— models, plusSOURCE_HEADER_BY_COLUMNandANALYTE_UNITSalembic/versions/d4e5f6a7b8c9_cm_legacy_mirror_tables.py— migration, single headservices/cm_legacy_mirror.py— loader, idempotent per sheetscripts/cm_reconciliation_report.py— builds the decision workbookoco load-critical-minerals-workbook --file <xlsx>docs/critical-minerals-legacy-mirror.md,CLAUDE.mdpointertests/test_cm_legacy.pyandtests/test_cm_reconciliation_report.pyWhy it looks like this
Three sheets, one table.
ChemicalDataandGISare byte-identical in header text and order (118 columns each);QAQCis those columns minusMapSymbol/Pd/Ptwithlatitude/longitudecapitalized. Only the layout differs, so one table behind asource_sheetdiscriminator beats three near-duplicate tables — and it makes reconciliation a self-join.Rows are keyed on
(source_sheet, source_row), notSAMPLE. Sample names repeat across 258 distinct names in the source, soSAMPLEcannot be the key.source_rowis the 1-based Excel row number, which makes every mirror row traceable to a cell range in the delivered workbook and a reload idempotent per sheet.Every column is a
String. The workbook stores censored analyte values as text (1154<values inAualone), carries#VALUE!errors, and mixes real dates with year-only text. Any typed column drops cells; parsing value-plus-qualifier and casting dates belong to the transform.Column names are derived, not copied. Sheet headers are spreadsheet labels, not SQL identifiers (
Chem Lab File No.,Depth/legnth (ft),H2O+), so unlike the NMA/NMW mirrors the source name cannot be reused verbatim. Analytes carry the unit the workbook declares for them (sio2_pct,au_ppb,as_ppm), which also keepsAsandInoff the Python and SQL keyword lists.SOURCE_HEADER_BY_COLUMNholds the exact header for all 118 columns.The load asserts its layout. Header rows are declared per sheet (ChemicalData 2, GIS 1, QAQC 2); a moved header row or an unmapped column aborts the load with nothing written, rather than silently dropping cells. A revised workbook that changes shape needs a human and a migration.
Reviewer should know: reconciliation is deferred on purpose
GISis notChemicalDataplus location data, and not a clean subset. It is a stale hand-maintained fork: same mixed datums, same ~876 rows with no latitude, so it adds no location information. EveryGISsample name exists inChemicalData, but 1704 of the 4848 shared rows disagree in both directions:GIShas valuesChemicalDatalacksChem Lab File No., 485Laboratory, 85FeO, 85Fe2O3ChemicalDatahas valuesGISlacksTotal, 184Area, 11TREE, 4Date analyzedArea(ZuniMountainsvsZuni)#VALUE!Totals inGIS, 1 inChemicalDataPlus 18 sample rows appended to
ChemicalDataafterGISwas last synced — all Pearce (2020), all with coordinates.Neither sheet is authoritative, so both are mirrored in full and the merge is left to a per-column ruling by V.T. McLemore. A query reading only
source_sheet='ChemicalData'silently drops 533 lab file numbers, 485 lab names and 170 FeO/Fe2O3 values. The loader warns whenever the two row counts diverge.The reconciliation report
scripts/cm_reconciliation_report.pywrites an 8-sheet decision workbook:ColumnDecisions(8),CellDifferences(2123),RowsOnlyInOneSheet(19),IntegritySummary(13),IntegrityDetail(4276),DetectionLimitSpread(64),DuplicateSampleNames(258). Every judgement column is blank with a dropdown.Integrity findings from
ChemicalDataalone include 724 values impossible for their declared unit (F= 27700 in a%column), 259Audetection limits that are ppm-scale in a ppb column, 300 non-numeric analyte tokens (bd,nd,tr,nr,----,>2%), 159Totals that disagree with the sum of the majors, and 64 of 84 analytes reported against more than three distinct detection limits.Totalis deliberately exempt from the unit check — it is a sum, so exceeding 100 is not itself a unit error; flagging it added 1519 false positives. OnlyChemicalDatais integrity-checked, since running the same checks overGISwould double every finding without adding information.Verification
101.83999999999999, not101.84), censored values keep their qualifier,#VALUE!passes through, dates become ISO-8601 instead of Excel serialsNotes
services/cm_legacy_mirror.pyexposescell_to_text,normalize_headerandcolumn_by_headerpublicly so the report reuses the mirror's exact cell rendering rather than reimplementing itoco load-critical-minerals-workbook) — like the other legacy mirrors, there is no CD path for data loads🤖 Generated with Claude Code